package org.jlamp.agent;

import io.netty.util.concurrent.DefaultThreadFactory;
import org.jlamp.core.runtime.JlampData;
import org.jlamp.core.util.JlampUtils;
import org.jlamp.loader.JlampClassLoader;
import org.jlamp.server.JlampStatus;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.*;

/**
 * Jlamp Agent
 *
 * @author zhangcs
 * @since 1.0
 */
@SuppressWarnings("unused")
public class JlampAgent {

    /**
     * 输出流
     */
    private static PrintStream logger = System.err;

    /**
     * JLamp进程根路径
     */
    private static String JLAMP_HOME;

    /**
     * 自定义类加载器，减少对原项目的侵入
     */
    private static volatile ClassLoader jlampClassLoader;

    private final static ThreadPoolExecutor listenerExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(), new DefaultThreadFactory("jlamp-listener-thread"));

    static {
        try {
            // home 路径
            JLAMP_HOME = JlampUtils.home();
            // 获取日志对象
            logger = JlampUtils.logger();
        } catch (Throwable t) {
            t.printStackTrace(logger);
        }
    }

    /**
     * Agent入口
     *
     * @param args jlamp-core.jar文件路径
     * @param inst instrumentation对象
     */
    public static void agentmain(String args, Instrumentation inst) {
        main(args, inst);
    }

    /**
     * Agent处理逻辑方法
     *
     * @param args jlamp-core.jar文件路径
     * @param inst instrumentation对象
     */
    public static synchronized void main(String args, Instrumentation inst) {
        // 1、防止重复初始化
        try {
            // class 不存在时抛出错误
            Class.forName("org.jlamp.server.JlampStatus");
            if (JlampStatus.isAlreadyLoaded()) {
                logger.println("JLamp server already stared, skip attach.");
                logger.flush();
                return;
            }
        } catch (Throwable e) {
            // ignore
        }

        logger.println("JLamp server agent start ...");

        // 2、加载 jlamp-core.jar 文件
        try {
            File file = new File(args);
            if (!file.exists()) {
                logger.println("JLamp server agent startup failed, the jlamp-server.jar file was not found!");
                logger.flush();
                return;
            }

            // 加载
            ClassLoader loader = getClassLoader(file);
            // 3、初始化服务
            ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<>(), new DefaultThreadFactory("jlamp-binding-thread"));
            Future<?> future = executor.submit(() -> {
                try {
                    bind(args, inst, loader);
                } catch (Throwable t) {
                    t.printStackTrace(logger);
                }
            });
            future.get();

            // 4、监听卸载服务
            listenerExecutor.execute(() -> {
                while (true) {
                    try {
                        JlampData data = JlampData.get();
                        String status = data.status();
                        if ("0".equals(status)) {
                            unBind(loader);
                            break;
                        }

                        logger.println("un bind, status=" + status);
                        Thread.sleep(5_000);
                    } catch (Throwable t) {
                        t.printStackTrace(logger);
                    }
                }
            });
        } catch (Throwable t) {
            t.printStackTrace(logger);
            try {
                if (logger != System.err) {
                    logger.close();
                }
            } catch (Throwable tt) {
                // ignore
            }
            throw new RuntimeException(t);
        }
    }

    private static final String BOOTSTRAP_CLASS = "org.jlamp.server.JlampBootstrap";

    /**
     * 绑定服务
     *
     * @param args   参数
     * @param inst   Instrumentation对象
     * @param loader 类加载器
     * @throws ClassNotFoundException    {@link org.jlamp.server.JlampBootstrap} 类无法找到时抛出异常
     * @throws NoSuchMethodException     {@link org.jlamp.server.JlampBootstrap#getInstance(Instrumentation, String)} 方法无法找到时抛出异常
     * @throws InvocationTargetException 方法执行失败时抛出异常
     * @throws IllegalAccessException    方法执行失败时抛出异常
     */
    private static void bind(String args, Instrumentation inst, ClassLoader loader) throws ClassNotFoundException, NoSuchMethodException,
            InvocationTargetException, IllegalAccessException {
        // 获取类 JlampBootstrap
        Class<?> bootstrapClass = loader.loadClass(BOOTSTRAP_CLASS);
        // 调用创建实例方法 getInstance(ins, args)
        Object bootstrap = bootstrapClass.getMethod("getInstance", Instrumentation.class, String.class)
                .invoke(null, inst, args);
        // 调用启用状态方法 isBind()
        boolean isBind = (Boolean) bootstrapClass.getMethod("isBind").invoke(bootstrap);
        if (!isBind) {
            String err = "JLamp server port binding failed! Please check " + JlampUtils.logPath() + " for more details.";
            logger.println(err);
            throw new RuntimeException(err);
        }
        logger.println("JLamp server already bind.");
    }

    /**
     * 取消绑定
     *
     * @param loader 类加载器
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    private static void unBind(ClassLoader loader) throws ClassNotFoundException, NoSuchMethodException,
            InvocationTargetException, IllegalAccessException {
        // 获取类 JlampBootstrap
        Class<?> bootstrapClass = loader.loadClass(BOOTSTRAP_CLASS);
        // 调用创建实例方法 getInstance(ins, args)
        Object bootstrap = bootstrapClass.getMethod("getInstance", Instrumentation.class, String.class)
                .invoke(null, null, null);
        // 调用销毁方法 destroy()
        bootstrapClass.getMethod("destroy").invoke(bootstrap);

        // 类加载器置为null
        loader = null;
        logger.println("JLamp server closed.");
    }

    /**
     * 获取类加载器
     *
     * @param jlampCoreFile 需要加载的jar文件
     * @return 返回类加载器
     * @throws MalformedURLException 加载jar文件失败时抛出此异常
     */
    private static ClassLoader getClassLoader(File jlampCoreFile) throws MalformedURLException {
        if (jlampClassLoader == null) {
            jlampClassLoader = new JlampClassLoader(new URL[]{jlampCoreFile.toURI().toURL()});
        }
        return jlampClassLoader;
    }

}
